;;; my-stock-ticker.el --- Show stock prices in mode line

;; Copyright (C) 2015 Gunther Hagleitner

;; Author: Gunther Hagleitner
;; Version: 0.1
;; Package-Version: 20150204.252
;; Keywords: comms
;; URL: https://github.com/hagleitn/my-stock-ticker
;; Package-Requires: ((s "1.9.0") (request "0.2.0"))

;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.

;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;; GNU General Public License for more details.

;; You should have received a copy of the GNU General Public License
;; along with this program.  If not, see <http://www.gnu.org/licenses/>.

;;; Commentary:
;;
;; my-stock-ticker lets you display stock prices in the mode line. You
;; can specify a number of stock symbols to track and their current
;; price, change and change percentage will be rotated in the mode
;; line.
;;
;; The financial data will be retrieved from Yahoo's finance apis via
;; YQL.

;;; Code:
;;(require 'json)
(require 'request)
;;(require 's)
(require 'timer)

(defun my-stock-ticker--query (symbols)
  "Generate yql query string from list of SYMBOLS."
  (let ((query-template "select * from yahoo.finance.quotes where symbol in (\"%s\")")
        (symbol-string (s-join "\",\"" symbols)))
    (format query-template symbol-string)))

;; (defun my-stock-ticker--parse (data)
;;   "Parse financial DATA into list of display strings."
;;   (let ((qs (assoc-default 'quote (assoc-default 'results (assoc-default 'query data)))))
;;     (mapcar
;;      (lambda (q)
;;        (let ((percent (assoc-default 'PercentChange q))
;;              (change (assoc-default 'Change q))
;;              (symbol (assoc-default 'Symbol q))
;;              (price (assoc-default 'LastTradePriceOnly q))
;;              (name (assoc-default 'Name q)))
;;          (format " %s: %s %s (%s) "
;;                  (if (or
;;                       (string-match "=" symbol)
;;                       (string-match "\\^" symbol)) name symbol)
;;                  (if price price "")
;;                  (if change change "")
;;                  (if percent percent ""))))
;;      qs)))

(defun my-stock-ticker--parse (data)
  "Parse financial DATA into list of display strings."
  (let* ((stock-val-str-list (split-string data "[=,]")) 
         (today-stock-begin-val-str (nth 2 stock-val-str-list))
         (today-stock-begin-val (string-to-number today-stock-begin-val-str))
         (yesterday-stock-val-str (nth 3 stock-val-str-list))
         (yesterday-stock-val (string-to-number yesterday-stock-val-str))
         (today-stock-val-str (nth 4 stock-val-str-list))
         (today-stock-val (string-to-number today-stock-val-str))
         (percent (* 100 (/ (- today-stock-val yesterday-stock-val) yesterday-stock-val))))
    (format " [%s %s %s %.2f%%]" today-stock-val-str today-stock-begin-val yesterday-stock-val-str percent)))

;;;###autoload
(defgroup my-stock-ticker nil
  "Stock ticker."
  :group 'applications
  :prefix "my-stock-ticker-")

;;;###autoload
(defcustom my-stock-ticker-symbols '("^gspc" "^dji" "^ixic" "^tnx"
				  "^nya" "XAUUSD=X" "EURUSD=X")
  "List of ticker symbols that the mode line will cycle through."
  :type '(string)
  :group 'my-stock-ticker)

;;;###autoload
(defcustom my-stock-ticker-name ""
  "The name of stock, example sh601766"
  :type 'string
  :group 'my-stock-ticker)

;;;###autoload
(defcustom my-stock-ticker-update-interval 5
  "Number of seconds between rest calls to fetch data."
  :type 'integer
  :group 'my-stock-ticker)

(defvar my-stock-ticker--current "")
(defvar my-stock-ticker--current-stocks nil)
(defvar my-stock-ticker--current-index 0)
(defvar my-stock-ticker--update-timer nil)
(defvar my-stock-ticker-count 0)

(defun my-stock-ticker--update ()
  "Update the global my-stock-ticker string."
  (request
   (format "http://hq.sinajs.cn/list=%s" (if (= 1 (length my-stock-ticker-name))
                                             (car my-stock-ticker-name) 
                                           (let ((stock-first-symbol (car my-stock-ticker-name))
                                                 (stock-other-symbol (cdr my-stock-ticker-name)))
                                             (setq my-stock-ticker-name (add-to-list 'stock-other-symbol stock-first-symbol t))
                                             stock-first-symbol)))
  ;;  :params `((q . ,(my-stock-ticker--query my-stock-ticker-symbols))
  ;;            (env . "http://datatables.org/alltables.env")
  ;;            (format . "json"))
   :parser 'buffer-string
   :success (function*
             (lambda (&key data &allow-other-keys)
               (when data
                 (progn (setq my-stock-ticker--current
                              (my-stock-ticker--parse data)))))))
  ;; (setq my-stock-ticker-count (+ my-stock-ticker-count 1))
  ;; (setq my-stock-ticker--current (format " %d" my-stock-ticker-count))
  (force-mode-line-update))

;;;###autoload
(define-minor-mode my-stock-ticker-global-mode
  "Add stock ticker info to the mode line.

Enabeling stock ticker global mode will add stock information in the form
SYMBOL: PRICE CHANGE (PERCENT CHANGE) to the mode line for each stock symbol
listed in 'my-stock-ticker-symbols'. Only one symbol is displayed at a time and
the mode cycles through the requested symbols at a configurable interval."
  :global t
  :group 'my-stock-ticker
  (setq my-stock-ticker--current "")
  (setq my-stock-ticker--current-index 0)
  (setq my-stock-ticker--current-stocks nil)
  (when (not global-mode-string) (setq global-mode-string '("")))
  (when my-stock-ticker--update-timer (cancel-timer my-stock-ticker--update-timer))
  (if (not my-stock-ticker-global-mode)
      (setq global-mode-string
            (delq 'my-stock-ticker--current global-mode-string))
    (add-to-list 'global-mode-string 'my-stock-ticker--current t)
    (setq my-stock-ticker--update-timer
          (run-at-time nil my-stock-ticker-update-interval
                       'my-stock-ticker--update))
    (my-stock-ticker--update)))

(provide 'my-stock-ticker)
;;; my-stock-ticker.el ends here
